#ifndef __BUOLA_GL_CTEXTURE_H__
#define __BUOLA_GL_CTEXTURE_H__

#include <buola/gl.h>
#include <buola/image/format.h>

namespace buola { namespace gl {

namespace detail {

template<typename tChannel>
struct MChannelInfo;

template<>
struct MChannelInfo<img::TChannel_8>
{
    static const GLenum mType=GL_UNSIGNED_BYTE;
};
    
template<>
struct MChannelInfo<img::TChannel_8s>
{
    static const GLenum mType=GL_BYTE;
};
    
template<>
struct MChannelInfo<img::TChannel_16>
{
    static const GLenum mType=GL_UNSIGNED_SHORT;
};
    
template<>
struct MChannelInfo<img::TChannel_16s>
{
    static const GLenum mType=GL_SHORT;
};
    
template<>
struct MChannelInfo<img::TChannel_32>
{
    static const GLenum mType=GL_UNSIGNED_INT;
};
    
template<>
struct MChannelInfo<img::TChannel_32s>
{
    static const GLenum mType=GL_INT;
};

template<>
struct MChannelInfo<img::TChannel_32f>
{
    static const GLenum mType=GL_FLOAT;
};

template<typename tColorspace>
struct MColorspaceInfo;

template<>
struct MColorspaceInfo<img::TColorSpace_rgb>
{
    static const GLenum mFormat=GL_RGB;
    static const GLenum mDefaultInternalFormat=GL_RGB;
};
    
template<>
struct MColorspaceInfo<img::TColorSpace_rgba>
{
    static const GLenum mFormat=GL_RGBA;
    static const GLenum mDefaultInternalFormat=GL_RGBA;
};
    
template<>
struct MColorspaceInfo<img::TColorSpace_bgr>
{
    static const GLenum mFormat=GL_BGR;
    static const GLenum mDefaultInternalFormat=GL_RGB;
};
    
template<>
struct MColorspaceInfo<img::TColorSpace_bgra>
{
    static const GLenum mFormat=GL_BGRA;
    static const GLenum mDefaultInternalFormat=GL_RGBA;
};
    
template<>
struct MColorspaceInfo<img::TColorSpace_gray>
{
    static const GLenum mFormat=GL_RED;
    static const GLenum mDefaultInternalFormat=GL_RED;
};

/*namespace detail*/ }
    
class CTexture
{
public:
    CTexture()
        :   mID(0)
    {}

    template<typename tFormat>
    explicit CTexture(const img::CView<tFormat> &pView)
        :   mID(0)
    {
        Create2D(pView);
    }

    explicit CTexture(const img::CGradient &pGradient)
        :   mID(0)
    {
        Create1D(pGradient);
    }
    
    ~CTexture()
    {
        if(mID) glDeleteTextures(1,&mID);
    }

    bool IsNull()   {   return mID==0;  }

private:
    void DoCreate1DAndBind();
    void DoCreate2DAndBind();

public:
    void CreateWithTarget(GLenum pTarget)
    {
        if(!mID) glGenTextures(1,&mID);
        mTarget=pTarget;
        Bind();
    }
    
    template<typename tFormat>
    void Create1D(const img::CView<tFormat> &pView,GLenum pInternalFormat=
                                detail::MColorspaceInfo<typename tFormat::TColorSpace>::mDefaultInternalFormat)
    {
        DoCreate1DAndBind();
        glTexImage1D(GL_TEXTURE_1D,0,pInternalFormat,pView.Width(),0,
                     detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                     detail::MChannelInfo<typename tFormat::TChannel>::mType,
                     pView.RawBytes());
    }
    
    void Create1D(const img::CGradient &pGradient);

    template<typename tFormat>
    void Create2D(const img::CView<tFormat> &pView,GLenum pInternalFormat=
                                detail::MColorspaceInfo<typename tFormat::TColorSpace>::mDefaultInternalFormat)
    {
        DoCreate2DAndBind();
        glTexImage2D(GL_TEXTURE_2D,0,pInternalFormat,pView.Width(),pView.Height(),0,
                     detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                     detail::MChannelInfo<typename tFormat::TChannel>::mType,
                     pView.RawBytes());
    }

    template<typename tFormat>
    void Create2D(const CSize_i &pSize,GLenum pInternalFormat=
                                detail::MColorspaceInfo<typename tFormat::TColorSpace>::mDefaultInternalFormat)
    {
        DoCreate2DAndBind();
        glTexImage2D(GL_TEXTURE_2D,0,pInternalFormat,pSize.x,pSize.y,0,
                     detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                     detail::MChannelInfo<typename tFormat::TChannel>::mType,nullptr);
    }

    void Create2D(const img::CAnyView &pSrc);

    template<typename tFormat>
    void CreateRectangle(const img::CView<tFormat> &pView,GLenum pInternalFormat=
                                detail::MColorspaceInfo<typename tFormat::TColorSpace>::mDefaultInternalFormat)
    {
        DoCreate2DAndBind();
        ///\todo: consider replacing it with glTexStorage2D
        glTexImage2D(GL_TEXTURE_RECTANGLE,0,GL_RGB,pView.Width(),pView.Height(),0,
                     detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                     detail::MChannelInfo<typename tFormat::TChannel>::mType,
                     pView.RawBytes());
    }

    template<typename tFormat>
    void Copy(const img::CView<tFormat> &pSrc,const CRect_i &pPos)
    {
        Bind();
        glPixelStorei(GL_UNPACK_ROW_LENGTH,pSrc.Stride());
        glTexSubImage2D(mTarget,0,pPos.l,pPos.t,pPos.w(),pPos.h(),
                        detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                        detail::MChannelInfo<typename tFormat::TChannel>::mType,pSrc.RawBytes());
    }

    template<typename tFormat>
    img::CImage<tFormat> GetImage(const CSize_i &pSize)
    {
        img::CImage<tFormat> lDst(pSize);
        Bind();
        glGetTexImage(mTarget,0,detail::MColorspaceInfo<typename tFormat::TColorSpace>::mFormat,
                        detail::MChannelInfo<typename tFormat::TChannel>::mType,lDst.RawBytes());
        return lDst;
    }
    
    void Bind()
    {
        glBindTexture(mTarget,mID);
    }

    void UnBind()
    {
        glBindTexture(mTarget,0);
    }

    GLuint ID() const       {   return mID;     }
    GLenum Target() const   {   return mTarget; }

    explicit operator bool()
    {
        return mID!=0;
    }

private:
    GLuint mID;
    GLenum mTarget;
};

/*namespace gl*/ } /*namespace buola*/ }

#endif
